package system

import (
	"context"
	"encoding/xml"
	"fmt"
	"math"
	"os"
	"os/exec"
	"runtime"
	"strings"
	"time"

	"github.com/gogf/gf/v2/net/gipv4"
	"github.com/gogf/gf/v2/os/glog"
	"github.com/gogf/gf/v2/os/gtime"

	"github.com/shirou/gopsutil/v3/cpu"
	"github.com/shirou/gopsutil/v3/disk"
	"github.com/shirou/gopsutil/v3/mem"
)

var System = system{}

type system struct {
	Cpu struct {
		Type string
	}
	Gpu struct {
		Type string
	}
	localIp  string
	hostname string
	cpuCores int

	lastGetCpuUsageAt *gtime.Time
	lastCpuUsage      int

	lastGetDiskUsageAt *gtime.Time
	lastDiskUsage      int

	lastMemUsage int

	AppStartUp *gtime.Time
}

func init() {
	t := gtime.Now()

	System.AppStartUp = t

	System.localIp = getIP()
	System.hostname = getHostname()

	System.cpuCores, _ = cpu.Counts(true)

	System.lastGetCpuUsageAt = t
	System.lastGetDiskUsageAt = t

	go func() {
		for {
			System.lastCpuUsage = getCpuUsage()
			time.Sleep(time.Second)
		}
	}()
	go func() {
		for {
			System.lastDiskUsage = getDiskUsage()
			time.Sleep(time.Second)
		}
	}()
	go func() {
		for {
			System.lastMemUsage = getMemoryUsage()
			time.Sleep(time.Second)
		}
	}()

	// setNodeType()
}

func (r *system) GpuInfo(ctx context.Context) string {
	if r.Gpu.Type == "" {
		r.Gpu.Type = r.GpuInfoNvidia(ctx)
	}

	return r.Gpu.Type
}
func (r *system) GpuInfoNvidia(ctx context.Context) string {
	out, err := exec.Command("nvidia-smi", "-q", "-x").Output()
	if err != nil {
		glog.Error(ctx, err)
		return GpuTypeUnknown
	}

	mi := nvidia{}
	if err := xml.Unmarshal(out, &(mi.NvidiaSmiLog)); err != nil {
		glog.Error(ctx, err)
		return GpuTypeUnknown
	}

	gpuType := GpuTypeUnknown

	if strings.Contains(mi.NvidiaSmiLog.Gpu.ProductName, "Tesla T4") {
		gpuType = GpuTypeNvidiaT4
	} else if strings.Contains(mi.NvidiaSmiLog.Gpu.ProductName, "Tesla A10") {
		gpuType = GpuTypeNvidiaA10
	} else if strings.Contains(mi.NvidiaSmiLog.Gpu.ProductName, "Tesla A100") {
		gpuType = GpuTypeNvidiaA100
	} else if strings.Contains(mi.NvidiaSmiLog.Gpu.ProductName, "Tesla V100") {
		gpuType = GpuTypeNvidiaV100
	} else {
		gpuType = GpuTypeUnknown
	}

	return gpuType
}
func (r *system) String() string {
	return fmt.Sprintf(`[appUptime="%d", ip="%s", hostname="%s", cpuCore="%d", cpuUsage="%d", diskUsage="%d", memUsage="%d"]`,
		r.AppUptime(), r.localIp, r.hostname, r.cpuCores, r.lastCpuUsage, r.lastDiskUsage, r.lastMemUsage,
	)
}
func (r *system) LocalIp() string {
	return r.localIp
}
func (r *system) Hostname() string {
	return r.hostname
}
func (r *system) CpuCores() int {
	return r.cpuCores
}
func (r *system) LastCpuUsage() int {
	return r.lastCpuUsage
}
func (r *system) LastDiskUsage() int {
	return r.lastDiskUsage
}
func (r *system) LastMemUsage() int {
	return r.lastMemUsage
}
func (r *system) AppUptime() int {
	return int(gtime.Now().Timestamp() - r.AppStartUp.Timestamp())
}

func getIP() string {
	localIp, _ := gipv4.GetIntranetIp()
	return localIp
}
func getHostname() string {
	hostname, _ := os.Hostname()
	return hostname
}
func getCpuUsage() int {
	diffInSeconds := gtime.Now().Sub(System.lastGetCpuUsageAt).Seconds()
	if diffInSeconds < 2 {
		return System.lastCpuUsage
	}
	System.lastGetCpuUsageAt = gtime.Now()

	cpuUsage0s, err := cpu.Percent(0, false)
	if err != nil {
		return System.lastCpuUsage
	}

	cpuUsage3s, err := cpu.Percent(3, false)
	if err != nil {
		return System.lastCpuUsage
	}

	cpuUsage5s, err := cpu.Percent(5, false)
	if err != nil {
		return System.lastCpuUsage
	}

	cpuUsage0 := cpuUsage0s[0]
	cpuUsage3 := cpuUsage3s[0]
	cpuUsage5 := cpuUsage5s[0]

	cpuUsage := math.Max(math.Max(cpuUsage0, cpuUsage3), cpuUsage5)

	return int(cpuUsage)
}
func getMemoryUsage() int {
	v, _ := mem.VirtualMemory()
	if v == nil {
		return 0
	}

	return int(v.UsedPercent)
}
func getDiskUsage() int {
	diffInSeconds := gtime.Now().Sub(System.lastGetDiskUsageAt).Seconds()
	if diffInSeconds < 10 {
		return System.lastDiskUsage
	}
	System.lastGetDiskUsageAt = gtime.Now()

	path := "c:\\"
	if runtime.GOOS != "windows" {
		path = "/"
	}
	diskState, _ := disk.Usage(path)
	return int(diskState.UsedPercent)
}
